/**
 *  Copyright (c) 2014 The xmlepf project.  All rights reserved.
 *
 *  This file is part of xmlepf (Event Driven Testing Framework).
 *
 *  xmlepf is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  xmlepf is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with xmlepf.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  This software consists of voluntary contributions made by many
 *  individuals.  For exact contribution history, see the revision
 *  history and logs, available at http://code.google.com/p/xmlepf.
 */
package org.xmlepf.xtemp.eventboard;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
import org.xmlepf.utils.ObjectCreator;
import org.xmlepf.xml.adapter.XMLTypeConvertor;
import org.xmlepf.xtemp.event.Event;

/**
 *
 *
 */
public class EventBoardImp implements EventBoard {

    private BlockingQueue<Event> eventQueue;
    private EventBoardInfo eventBoardInfo;
    private final Object eventLock = new Object();
    private boolean shouldContinueCatching;

    public EventBoardImp() {
        eventQueue = new LinkedBlockingQueue<Event>();
    }

    @Override
    public void setInfo(EventBoardInfo eventBoardInfo) {
        this.eventBoardInfo = eventBoardInfo;
    }

    @Override
    public EventBoardInfo getInfo() {
        return eventBoardInfo;
    }

    @Override
    public void postEvent(Event event) {
        eventQueue.add(event);
    }

    @Override
    public void postEvents(List<Event> eventNodes) {
        eventQueue.addAll(eventNodes);
    }

    @Override
    public Event catchEvent(String xPathExpression, long maxTimeout) throws Exception {

        long catchStartTime = System.currentTimeMillis();

        shouldContinueCatching = true;
        // note: performance inefficient query mechanism, suitable for a prototype implementation only!
        // todo: implement a registration / notification mechanism for being able to scale to 
        //       (tens of) thousands of events. Use parallel queries, too.
        while (shouldContinueCatching) {
            for (Event event : eventQueue) {
                if (event.isMasked() == false) {
                    if (event.query(xPathExpression)) {
                        synchronized (eventLock) {
                            if (event.isMasked() == false) {
                                return event;
                            }
                        }
                    }
                }
                
                long timeElapsedSoFar = System.currentTimeMillis() - catchStartTime;
                if ( timeElapsedSoFar >= maxTimeout) {
                        XMLTypeConvertor xmlConvertor = (XMLTypeConvertor) ObjectCreator.get( "XMLTypeConvertor" );
                        String userSpecifiedTimeoutAsISO8601 = xmlConvertor.millisecondsToXsdDayTimeDurationISO8601(maxTimeout);
                        String timeElapsedAsISO8601 = xmlConvertor.millisecondsToXsdDayTimeDurationISO8601(timeElapsedSoFar);
                        throw new TimeoutException( "Catching events using query '" + xPathExpression + 
                                "' on the event board '" + eventBoardInfo.getName() + 
                                "' timed-out. User-specified @tryfor timeout was '" + userSpecifiedTimeoutAsISO8601 + 
                                "'. Actual time spent was '" + timeElapsedAsISO8601 + "'." );
                }
            }
            long time2sleepInMs = Math.min(500L, Math.abs(maxTimeout - (System.currentTimeMillis() - catchStartTime)));
            Thread.sleep(time2sleepInMs);
        }

        throw new Exception("Catching was canceled for board '" + eventBoardInfo.getName()
                + "' using filter condition '" + xPathExpression + "'.");
    }

    @Override
    public List<Event> catchEvents(String xPathExpression, long maxTimeout) throws Exception {

        long catchStartTime = System.currentTimeMillis();

        shouldContinueCatching = true;
        List<Event> eventList = new LinkedList<Event>();

        // note: performance inefficient query mechanism, suitable for a prototype implementation only!
        // todo: implement a registration / notification mechanism for being able to scale to 
        //       (tens of) thousands of events. Use parallel queries, too.
        while (shouldContinueCatching) {
            for (Event event : eventQueue) {
                if (event.isMasked() == false) {
                    if (event.query(xPathExpression)) {
                        synchronized (eventLock) {
                            if (event.isMasked() == false) {
                                eventList.add(event);
                            }
                        }
                    }
                }

                long timeElapsedSoFar = System.currentTimeMillis() - catchStartTime;
                if ( timeElapsedSoFar >= maxTimeout) {
                    if (eventList.isEmpty()) {
                        XMLTypeConvertor xmlConvertor = (XMLTypeConvertor) ObjectCreator.get( "XMLTypeConvertor" );
                        String userSpecifiedTimeoutAsISO8601 = xmlConvertor.millisecondsToXsdDayTimeDurationISO8601(maxTimeout);
                        String timeElapsedAsISO8601 = xmlConvertor.millisecondsToXsdDayTimeDurationISO8601(timeElapsedSoFar);
                        throw new TimeoutException( "Catching events using query '" + xPathExpression + 
                                "' on the event board '" + eventBoardInfo.getName() + 
                                "' timed-out. User-specified @tryfor timeout was '" + userSpecifiedTimeoutAsISO8601 + 
                                "'. Actual time spent was '" + timeElapsedAsISO8601 + "'." );
                    } else {
                        // return the events catched so far
                        return eventList;
                    }
                    
                }
            }
            if (eventList.size() > 0) {
                return eventList;
            } else {
                long time2sleepInMs = Math.min(500L, Math.abs(maxTimeout - (System.currentTimeMillis() - catchStartTime)));
                Thread.sleep(time2sleepInMs);
            }
        }

        throw new Exception("Catching was canceled for board '" + eventBoardInfo.getName()
                + "' using filter condition '" + xPathExpression + "'.");
    }

    @Override
    public void stopCatchingEvents() {
        shouldContinueCatching = false;
    }

    @Override
    public synchronized int getEventCounter() {
        return eventQueue.size();
    }

    @Override
    public synchronized int getMaskedEventCounter() {
        int counter = 0;
        for (Event event : eventQueue) {
            if (event.isMasked()) {
                counter++;
            }
        }
        return counter;
    }

    @Override
    public synchronized int getNonMaskedEventCounter() {
        int counter = 0;
        for (Event event : eventQueue) {
            if (event.isMasked() == false) {
                counter++;
            }
        }
        return counter;
    }

    @Override
    public boolean isMatchingEvent(Event event) throws Exception {
        // all the conditions specified in the //event-board/filter should match:
        for (EventBoardFilter boardFilter : eventBoardInfo.getFilters()) {
            String condition = boardFilter.getCondition();
            if (!event.query(condition)) {
                return false;
            }
        }
        return true;
    }
}
